/*
    日志落地模块---负责将格式化字符串输出到指定的位置上
    指定位置：
        1.标准输出---常用于内部测试/debug
        2.指定文件---将日志消息输出到指定文件内部
        3.滚动文件---按照时间/文件大小，将日志滚动式的切换到不同的文件中，进行输出。防止单一文件过大的问题
    实现思想：借助工厂设计模式
        1.抽象出一个基类
        2.有该基类派生出其他不同的落地模式
*/

#pragma once
#include "util.hpp"
#include <fstream>
#include <sstream>
#include <memory>
#include <cassert>

namespace logging
{
    // 基类落地
    // 指向其他的派生落地方向
    // 往后用户自己想要派生出其他的落地方向，可以用该基类指针指向
    class sink
    {
    public: 
        using s_ptr = std::shared_ptr<sink>;
        virtual ~sink() {}
        virtual void logSink(const char *data, size_t len) = 0;
    };

    // 落地方向：标准输出
    class stdoutSink : public sink
    {
    public:
        void logSink(const char *data, size_t len) override { std::cout.write(data, len); }
    };

    // 落地方向：指定文件
    class fileSink : public sink
    {
    public:
        fileSink(const std::string &pathname)
            :_pathname(pathname)
        {
            // 1.创建指定目录
            util::File::createDirectory(util::File::getFilePath(_pathname));

            // 2.打开指定文件
            // 以二进制 追加方式打开文件
            _ofs.open(_pathname, std::ios::binary | std::ios::app);

            // 3.判断文件是否打开成功
            assert(_ofs.is_open());
        }
        void logSink(const char *data, size_t len) override 
        { 
            _ofs.write(data, len);
            assert(_ofs.good());
        }
    private:
        std::string _pathname;
        std::ofstream _ofs;
    };

    // 落地方向：滚动文件——通过大小切换文件
    class rollSinkBySize : public sink
    {
    public:
        rollSinkBySize(const std::string &basename, size_t maxFileSize)
            :_basename(basename), _max_fsize(maxFileSize), _cur_fsize(0), _name_count(0)
        {
            // 1.构建完整文件名
            std::string filename = createNewFile();

            // 2.创建指定目录
            util::File::createDirectory(util::File::getFilePath(filename));

            // 3.打开指定文件
            // 以二进制 追加方式打开文件
            _ofs.open(filename, std::ios::binary | std::ios::app);

            // 4.判断文件是否打开成功
            assert(_ofs.is_open());
        }
        void logSink(const char *data, size_t len) override
        {
            // 1.写入前判断当前文件大小是否已经超过范围
            if(_cur_fsize >= _max_fsize) {
                // 1.1 关闭原来的文件
                _ofs.close();

                //1.2 创建一个新的文件
                std::string newFile = createNewFile();

                // 1.3打开该文件
                _ofs.open(newFile, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_fsize = 0; // 打开新文件后，将当前文件大小清空
            }

            // 2.写入日志
            _ofs.write(data, len);
            _cur_fsize += len;
        }
    private:
        // 切换文件
        std::string createNewFile()
        {
            // 通过基础文件名，创建出一个具体的带有时间的文件名
            // 1.获取当前时间
            time_t t = util::Date::getTime();
            struct tm tmset;
            localtime_r(&t, &tmset);

            // 将基础文件名与时间后缀拼接构成一个完整的文件名
            std::stringstream ss;
            ss << _basename;
            ss << tmset.tm_year + 1900;
            ss << tmset.tm_mon + 1;
            ss << tmset.tm_mday;
            ss << tmset.tm_hour; 
            ss << tmset.tm_min;
            ss << tmset.tm_sec;
            ss << "-";
            ss << _name_count++; 
            ss << ".log";
            return ss.str();
        }
    private:
        std::string _basename;  // 基础文件名：./logs/base-xxxx(时间).log ->  ./logs/base-20250601101832.log
        std::ofstream _ofs;     // 用于管理文件的句柄
        size_t _max_fsize;      // 最大文件的大小，超过该大小就要切换一个新的文件
        size_t _cur_fsize;      // 当前文件的大小
        size_t _name_count;
    };

    // 借助简单工厂模式，实现生成不同的日志落地模式
    // 再借助模板参数和不定参函数，来实现创建不同的参数的函数
    // 如果设置成模板类，会导致要传入多个类型，不方便
    // 所以这里将模板类，改为静态模板
    //template <typename sinkType, typename ...Args>
    class sinkFactory
    {
    public:
        template <typename sinkType, typename ...Args>
        static sink::s_ptr create(Args &&...args)
        {
            return std::make_shared<sinkType>(std::forward<Args>(args)...);
        }
    };
}